import os
import re
import time
from threading import Thread, Lock
import asyncio
import multiprocessing

import aiohttp
import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

LOCK = Lock()


async def fetch(session, ts_url, download_path, key, iv, fail_li):
    ts_name = re.findall('_._(.*?)\\?pid', ts_url)[0]

    if os.path.exists(os.path.join(download_path, ts_name)):
        print('文件已存在!!')
        return
    while True:

        try:
            async with session.get(ts_url, verify_ssl=False, timeout=300) as response:

                data = await response.content.read()

        except Exception as f:
            print(f'[{ts_name}] ts_url fail ', ts_url)
            continue

        decry_aes(key=key, iv=iv, download_path=download_path, ts_name=ts_name, data=data)
        break


async def asmain(url_list, download_path, key, iv, fail_li):
    async with aiohttp.ClientSession() as session:
        tasks = [asyncio.create_task(fetch(session, url, download_path, key, iv, fail_li)) for url in url_list]

        done, pending = await asyncio.wait(tasks)


def decry_aes(key, iv, download_path, ts_name, data):
    iv = bytes.fromhex(iv)
    print(iv)
    if len(key):  # AES 解密
        cryptor = AES.new(key, AES.MODE_CBC, iv)
        try:
            if len(data) % 16:
                print('需要填充！！！')
                BLOCK_SIZE = 16

                with open(os.path.join(download_path, ts_name), 'ab') as f:
                    f.write(cryptor.decrypt(pad(data, BLOCK_SIZE)))
                print(f'{ts_name}写入成功')
            else:
                with open(os.path.join(download_path, ts_name), 'ab') as f:
                    f.write(cryptor.decrypt(data))
                print(f'{ts_name}写入成功')
        except Exception as f:
            print(f'{ts_name}写入失败', f)



def download(url, title, course_name, chapter_name, token, chapter_index, title_index):
    if '&' in title:
        title = title[:title.find('&')] + title[title.find('&')+1:]
    iv = ''
    LOCK.acquire()  # 加锁  保护os执行路径安全
    download_path = os.getcwd() + f"\\{course_name}_mp4"
    LOCK.release()  # 解锁
    if not os.path.exists(download_path):
        try:
            os.mkdir(download_path)
        except FileExistsError:
            pass
    # 新建章节文件夹
    download_path = os.path.join(download_path, f'{chapter_index}-{chapter_name}')

    if not os.path.exists(download_path):

        try:
            os.mkdir(download_path)
        except FileExistsError:
            pass

    download_path = os.path.join(download_path, f'{title_index}-{title}')

    if not os.path.exists(download_path):

        try:
            os.mkdir(download_path)
        except FileExistsError:
            pass
    if os.path.exists(os.path.join(download_path, f"{title}.mp4")):
        print("mp4文件已存在")
        return
    all_data = requests.get(url, timeout=18).text  # 获取M3U8文件内容
    if "#EXTM3U" not in all_data:
        raise BaseException("非M3U8的链接")

    data_line = all_data.split("\n")

    unknow = True
    key = ""  # # 秘钥
    fail_li = []  # ts_url出错列表
    ts_li = []
    for index, line in enumerate(data_line):
        if "#EXT-X-KEY" in line:  # 找解密Key和iv

            index_l = line.find("URI")
            index_r = line.rfind('"')
            key_path = line[index_l:index_r].split('"')[1]
            iv = line.split('IV=')[-1].split('0x')[-1]
            print('iv', iv, len(iv))
            key_url = key_path + "?token=" + token  # 拼出key解密密钥URL
            try:
                res = requests.get(key_url, timeout=18)  # 拿到key
                key = res.content
            except Exception as f:
                print("访问key_url fail", f)
                return
            else:
                print("key：", key)

        if "EXTINF" in line:  # 找ts地址并下载
            unknow = False
            ts_url = data_line[index + 1]  # 拼出ts片段的URL
            ts_li.append(ts_url)
    sec = length = len(ts_li)
    if length > 5:
        sec = 5
    li = [ts_li[i:i + sec] for i in range(0, length, sec)]
    for i in li:
        asyncio.run(asmain(i, download_path, key, iv, fail_li))

    # if fail_li:
    #     print('处理失败url')
    #     for index, ts_url in enumerate(fail_li):
    #         ts_name = re.findall('_._(.*?)\\?pid', ts_url)[0]
    #         msg = 1
    #         while msg:
    #             try:
    #                 print(f'第{msg}次尝试！！')
    #                 data = requests.get(ts_url, timeout=18).content
    #             except Exception as f:
    #                 print(f'[fail_{ts_name}] ts_url fail ', f)
    #                 msg += 1
    #                 continue
    #             else:
    #                 msg = 0
    #                 decry_aes(key=key, iv=iv, download_path=download_path, ts_name=ts_name, data=data)
    if unknow:
        raise BaseException("未找到对应的下载链接")
    else:
        print("下载完成")

    merge_file(download_path, title)


def merge_file(path, title):

    file_li = os.listdir(path)

    file_li.sort(key=lambda x: int(x[:-3]))

    sec = length = len(file_li)
    if length > 400:
        sec = 400
    li = [file_li[i:i + sec] for i in range(0, length, sec)]
    cmd_a = ''
    for d, i in enumerate(li):

        cmd = ''
        for index, v in enumerate(i):
            if index == 0:
                cmd = v
            else:
                cmd = cmd + '+' + v
        cmd = f'copy /b {cmd} "{title}_{d}"'
        LOCK.acquire()
        path_1 = os.getcwd()
        os.chdir(path)
        os.system(cmd)

        if os.path.exists(f'{title}_{d}'):
            print('中间合并文件成功')
            if d == 0:
                cmd_a = f'"{title}_{d}"'
            else:
                cmd_a = cmd_a + '+' + f'"{title}_{d}"'
        else:

            print('中间文件合并失败！', f'{title}_{d}')
        os.chdir(path_1)
        LOCK.release()
    cmd = f'copy /b {cmd_a} "{title}.mp4"'

    LOCK.acquire()
    path_1 = os.getcwd()
    os.chdir(path)
    os.system(cmd)
    if os.path.exists(f'{title}.mp4'):
        os.system('del *.ts')
        os.system('del *_?')
    print('合并文件成功')
    os.chdir(path_1)
    LOCK.release()


def get_url(access_token, unit_id):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36',
        'Origin': 'https://www.luffycity.com',
        'Referer': f'https://www.luffycity.com/',
        'authorization': f'Bearer {access_token}'
    }
    res = requests.get(f'https://api.luffycity.com/api/v1/play/{unit_id}/?play_id={unit_id}', headers=headers,
                       timeout=18).json()
    if not res['data']['auth_info']['vid']:
        raise BaseException('拿m3u8 vid fail')
    return res['data']['auth_info']


def get_course_li(access_token):
    url = f'https://api.luffycity.com/api/v1/study/courses/'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36',
        'Origin': 'https://www.luffycity.com',
        'Referer': f'https://www.luffycity.com/',
        'authorization': f'Bearer {access_token}'
    }
    try:
        res = requests.get(url=url, headers=headers, timeout=18).json()

        li = res['data']
        course_li = []
        for i in li:
            course_li.append({'id': i['id'], 'name': i['name']})
    except:
        raise BaseException('取课程列表 fail')

    return course_li


def get_chapters(course_id, access_token):
    video_li = []
    url = f'https://api.luffycity.com/api/v1/study/module/actual/{course_id}/?courseType=actual&id={course_id}'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36',
        'Origin': 'https://www.luffycity.com',
        'Referer': f'https://www.luffycity.com/',
        'authorization': f'Bearer {access_token}'
    }
    try:
        res = requests.get(url=url, headers=headers, timeout=18).json()
        chapters = res['data']['chapters']
        for n, i in enumerate(chapters):
            video_li.append({'name': i['name'], 'videos': []})
            for v in i['sections']:
                video_li[n]['videos'].append({'id': v['id'], 'name': v['name']})
        return video_li
    except:

        raise BaseException('取video id fail')


def start_thread(i, access_token, course_name, chapter_name, chapter_index, title_index):
    unit_id = i['id']
    title = i['name']
    auth_info = get_url(access_token, unit_id=unit_id)
    vid = auth_info['vid']
    token = auth_info['token']  # key的token
    if vid:
        print('取m3u8 url success')
    print(vid)
    res = requests.get(
        f'https://hls.videocc.net/{vid[:10]}/f/{vid[:-2]}.m3u8?device=desktop&pid={int(time.time() * 1000)}X1475729').text
    m3u8_li = res.split('\n')
    if '#EXTM3U' not in m3u8_li:
        raise BaseException('非m3u8文件')

    m3u8_url = f'https://hls.videocc.net/{vid[:10]}/5/{m3u8_li[2]}'
    print(m3u8_url)
    if '?' in chapter_name:
        chapter_name = chapter_name.split('?')[0]
    if '?' in title:
        title = title.split('?')[0]
    download(m3u8_url, title, course_name, chapter_name, token, chapter_index, title_index)


def start_process(access_token, value):
    course_name = value['name']  # 课程名字
    course_id = value['id']  # 课程ID
    video_li = get_chapters(course_id, access_token)

    pool = multiprocessing.Pool(10)
    for chapter_index, v in enumerate(video_li):
        chapter_name = v['name']

        for title_index, i in enumerate(v['videos']):
            pool.apply_async(start_thread,
                                  (i, access_token, course_name, chapter_name, chapter_index, title_index))


    pool.close()
    pool.join()



def main():
    access_token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjM5Mzc0MDk0LCJpYXQiOjE2Mzg5NDIwOTQsImp0aSI6IjllYmE0NmJlZWIxNTQ0YTY5Mjk2YzRlZWQwNzVlODExIiwidWlkIjoiZmY1NTQ2OWZmOTc2YzU5NGM4MjU2Y2Y3NDA5MzkyYjMifQ.l2e28jAwywfgyswoZhdEjKauQxZkY2tTESL10ME55n0'  # 登录
    course_li = get_course_li(access_token)
    course_li.reverse()
    print(course_li)
    for value in course_li:
        start_process(access_token, value)
        # Process(target=start_process, args=(access_token, value, index)).start()


if __name__ == '__main__':
    multiprocessing.freeze_support()
    main()
